use anyhow::Ok;
use image::{Rgb, RgbImage};
use opencv::{
    core::{Size, Vec3b},
    imgcodecs,
    imgproc::{self, InterpolationFlags},
    prelude::*,
};
use std::io::prelude::*;

pub fn save_rgb_img_width_frame(path: &str, frame: &Mat) -> anyhow::Result<()> {
    let mut img_data = Vec::<Vec<(u8, u8, u8)>>::new();

    for row in 0..frame.rows() {
        let mut row_data = Vec::new();
        for col in 0..frame.cols() {
            let _data = frame.at_2d::<Vec3b>(row, col)?;
            let _data = _data.0;
            let (b, g, r) = (_data[0], _data[1], _data[2]);
            row_data.push((r, g, b));
        }
        img_data.push(row_data);
    }
    println!("save_rgb_img...");
    save_rgb_img(path, img_data)?;

    anyhow::Ok(())
}

/**
 * 保存灰度图
 */
pub fn save_gray_img_width_frame(path: &str, frame: &Mat) -> anyhow::Result<()> {
    let mut img_data = Vec::<Vec<(u8, u8, u8)>>::new();

    for row in 0..frame.rows() {
        let mut row_data = Vec::new();
        for col in 0..frame.cols() {
            let _data = frame.at_2d::<u8>(row, col)?;
            let _data = _data.clone();
            row_data.push((_data, _data, _data));
        }
        img_data.push(row_data);
    }
    save_rgb_img(path, img_data)?;

    anyhow::Ok(())
}

/**
 * 保存图片
 */
pub fn save_rgb_img(path: &str, rows_rgb_data: Vec<Vec<(u8, u8, u8)>>) -> anyhow::Result<()> {
    let height = rows_rgb_data.len();
    let width = rows_rgb_data.get(0).map(|p| p.len()).unwrap_or(0);

    let mut img = RgbImage::new(width as u32, height as u32);
    for row in 0..height {
        for col in 0..width {
            let _data = rows_rgb_data.get(row).unwrap().get(col).unwrap();
            let rgb = Rgb([_data.0, _data.1, _data.2]);
            img.put_pixel(col as u32, row as u32, rgb);
        }
    }
    anyhow::Ok(img.save(path)?)
}

/**
 * 缩放
 */
pub fn cvt_resize_mat(src: &Mat, width: i32, height: i32) -> anyhow::Result<Mat> {
    let mut dist = Mat::default();
    let size = Size::new(width, height);
    imgproc::resize(
        src,
        &mut dist,
        size,
        0_f64,
        0_f64,
        InterpolationFlags::INTER_NEAREST_EXACT as i32,
    )?;

    Ok(dist)
}

/**
 * 读取图片MAT
 */
pub fn read_img_mat_from_path(path: &str) -> anyhow::Result<Mat> {
    let mut f = std::fs::File::open(path)?;

    let mut buff = Vec::<u8>::new();
    f.read_to_end(&mut buff).unwrap();

    let src_mat = Mat::from_slice::<u8>(&buff[..])?;
    let dest = imgcodecs::imdecode(&src_mat, imgcodecs::IMREAD_COLOR)?;

    Ok(dest)
}

/**
 * 计算图片的Hash值
 */
pub fn img_hash_str(src: &Mat) -> anyhow::Result<String> {
    let size = Size::new(32, 32);

    // # 转换为灰度图
    let mut tar_mat = Mat::default();
    imgproc::cvt_color(&src, &mut tar_mat, imgproc::COLOR_BGR2GRAY, 0)?;

    // # 缩放
    let tar_mat = cvt_resize_mat(&tar_mat, size.width, size.height)?;

    // 初始: 像素值和 ，hash_str为hash值初值为''
    let mut pixel_count = 0;
    let mut hash_str = String::new();

    // # 遍历累加求像素和
    for x in 0..size.width {
        for y in 0..size.height {
            let pixel = tar_mat.at_2d::<u8>(y, x)?;
            pixel_count += pixel.clone() as u32;
        }
    }
    // # 求平均灰度
    let pixel_val_avg = pixel_count / (size.width * size.height) as u32;

    // # 灰度大于平均值为1相反为0生成图片的hash值
    for x in 0..size.width {
        for y in 0..size.height {
            let pixel = tar_mat.at_2d::<u8>(y, x)?;
            // 因为是灰度,rgb的三项值一致,故而取第一个值即可
            let pixel_val = pixel.clone() as u32;
            // 计算并添加hash值
            hash_str.push_str(if pixel_val > pixel_val_avg { "1" } else { "0" });
        }
    }

    Ok(hash_str)
}

/**
 * Hash值对比
 */
pub fn cmp_img_hash(hash1: &str, hash2: &str) -> anyhow::Result<f32> {
    // # hash长度不同则返回-1代表传参出错
    if hash1.len() != hash2.len() {
        anyhow::bail!("两个HASH长度不一致");
    }

    // # 遍历判断
    let mut score = 0u32;

    let hash2_chs = hash2.chars().collect::<Vec<char>>();
    for (index, ch1) in hash1.chars().enumerate() {
        let ch2 = hash2_chs.get(index).unwrap();
        score += if ch1.eq(ch2) { 1 } else { 0 };
    }

    Ok(score as f32 / hash1.len() as f32)
}
